#include "../include/hash_table.h"

HashTable::HashTable(int size) :
  buckets_size_(size), table_(NULL), entry_num_(0), used_buckets_(0) {
  table_ = new HashEntry*[buckets_size_]; //TODO
  for (int i = 0; i < buckets_size_; ++i)
    table_[i] = NULL;
}

HashTable::~HashTable() {
  for (int i = 0; i < buckets_size_; ++i) {
    HashEntry* he = table_[i];
    while (he) {
      delete []he->key;
      delete he;
      he = he->next;
    }
  }

  delete []table_;
}

unsigned int HashTable::hash_fun(const char* key) const {
  unsigned int sum = 0;
  size_t len = strlen(key);
  for (int i = 0; i < len; ++i)
    sum = ((sum << 5) + sum + key[i]); //TODO

  return sum % buckets_size_;
}

int HashTable::insert(const char* key, int& word_id) {
  int index = hash_fun(key);
  assert(index >= 0);
  if (table_[index] == NULL) {
    table_[index] = new HashEntry; 
    table_[index]->key = key;
    table_[index]->word_id = word_id++;
    table_[index]->df = 1;
    table_[index]->next = NULL;
    ++entry_num_;
    ++used_buckets_;
    return word_id - 1;
  } else {
    HashEntry* pe = table_[index];
    HashEntry* pre = pe;
    while (pe) {
      if (strcmp(pe->key, key) == 0)
        break;
      pre = pe;
      pe = pe->next;
    }
    if (pe == NULL) { //new key conflict
      pre->next = new HashEntry;
      pre->next->key = key;
      pre->next->word_id = word_id++;
      pre->next->df = 1;
      pre->next->next = NULL;
      ++entry_num_;
      return word_id - 1;
    } else { //key already exist
      ++pe->df;
      return pe->word_id;
    }
  }
}

int HashTable::get_id(const char* key) const {
  int index = hash_fun(key);
  HashEntry* he = table_[index];

  while (he) {
    if (strcmp(he->key, key) == 0) {
      return he->word_id;
    }
    he = he->next;
  }
  return -1;
}

/*
int HashTable::get_df(const char* key) const {
  int index = hash_fun(key);
  HashEntry* he = table_[index];

  while (he) {
    if (strcmp(he->key, key) == 0)
      return he->df;
    he = he->next;
  }
  return 0;
}
*/

int HashTable::save(const std::string& file) const {
  std::ofstream fout(file.c_str(),std::ofstream::out);
  if (!fout) {
    std::cerr<<"can not open"<<file<<std::endl;
    return -1;
  }
  
  for (int i = 0; i < buckets_size_; ++i) {
    HashEntry* he = table_[i];
    while (he) {
      fout << he->key << " " << he->word_id << " "<< he->df << std::endl;
      he = he->next;
    }
  }

  fout.close();
  return 0;
}

void HashTable::stat() const {
  std::cout << "used_buckets_(U): " << used_buckets_ << std::endl;
  std::cout << "buckets_size_(S): " << buckets_size_ << std::endl;
  if (buckets_size_ == 0)
    return;
  std::cout << "Factor(U/S): " << (float) used_buckets_ / buckets_size_
      << std::endl;

  std::cout << "entry_num_(E): " << entry_num_ << std::endl;
  std::cout << "conflict num(E-U): " << entry_num_ - used_buckets_ << std::endl;
  if (used_buckets_ == 0)
    return;
  std::cout << "conflict Ratio((E-U)/U): "
      << (float) (entry_num_ - used_buckets_) / used_buckets_ << std::endl;
  return;
}

void HashTable::show() const {
  for (int i = 0; i < buckets_size_; ++i) {
    HashEntry* he = table_[i];
    while (he) {
      std::cout << "key: " << he->key << "\tid: " << he->word_id << "\ttf: "
          << he->df << std::endl;
      he = he->next;
    }
  }
}
